iT邦幫忙

2024 iThome 鐵人賽

DAY 16
0
Modern Web

我阿嬤都會的 kintone 客製化開發系列 第 16

Day 16 | 客製化 kintone 入口網站

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20240921/20139057EVjETmcnPZ.jpg

在登入後進入到 kintnoe 主畫面後,都會導向 https://test.cybozu.com/k/#/portal 這個網址,這個網址顯示的畫面就是入口網站,會出現系統預設的畫面,當中有公告欄、未處理事項、應用程式等等區塊,這些區塊的增加或減少可以從「入口網站的設定」中調整:

下方的勾選方框如果未勾選,該區塊就不會顯示,所以我們可以利用這個特性,把全部的區塊都關掉,再自己開發入口網站的介面。雖然你可能覺得會有點多此一舉,但通過自己設計的話彈性會非常高,而且比較漂亮。這篇文章會做一個簡易版的手機選單,下方可以點選切換畫面:

portal 開發的檔案要放在哪裡

不同於應用程式的開發,portal 開發要將 JS 及 CSS 放在以下:

kintone系統管理 > 透過JavaScript/CSS自訂

取得 portal DOM

把首頁全部的區塊關掉後,我們要做的第一件事就是找到官方提供找 portal 的 DOM 的方法:

  • 電腦:kintone.portal.getContentSpaceElement()
  • 手機:kintone.mobile.portal.getContentSpaceElement()

開發的時候記得要根據裝置抓不同的 DOM,至於畫面的排版就交給 CSS Media Query 就好了。

不過要注意的是關閉首頁區塊並沒有區分電腦和手機版,關掉的話就是兩個一起關。這邊為了方便,以下的範例就只有寫手機版。

開發程式碼

這邊我用 React + TS 開發,同時會用到 React Router 和 Redux。

// index.tsx
import ReactDOM from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import router from './router'

kintone.events.on('mobile.portal.show', () => {
  const rootEl = kintone.mobile.portal.getContentSpaceElement()!
  const root = ReactDOM.createRoot(rootEl)
  root.render(<RouterProvider router={router} />)
})

如果不用 Router,直接用 useState 紀錄當前要顯示哪個組件也可以。Redux 則是因為很多頁其實都抓相同的資料,沒必要再取一次,例如待處理清單。

@reduxjs/toolkit

因為第一次在 redux 裡面寫非同步,也是研究了一下才知道要怎麼用:

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import { getUnsettledItems } from '../../api'
import type { UnsettledItem } from '../../type'

type SliceState = {
  unsettledList: UnsettledItem[]
  fetched: boolean
}

const initialState: SliceState = {
  unsettledList: [],
  fetched: false,
}

// 定義異步 thunk
export const fetchUnsettledList = createAsyncThunk(
  'unsettledList/fetchUnsettledList',
  async () => {
    const response = await getUnsettledItems({ includeGuestInfo: true })
    const { assignedAppList } = response.data.result
    return assignedAppList
  },
)

export const unsettledListSlice = createSlice({
  name: 'unsettledList',
  initialState,
  reducers: {},
  extraReducers(builder) {
    builder
      .addCase(fetchUnsettledList.fulfilled, (state, action) => {
        if (state.fetched) return
        state.unsettledList = action.payload
        state.fetched = true
      })
      .addCase(fetchUnsettledList.rejected, (state) => {
        console.log(state)
      })
  },
})

export const selectList = (state: { unsettledList: SliceState }) => state.unsettledList
export default unsettledListSlice.reducer

上方有先定義了 fetched 來判斷是否有拿過資料了,因此在組件中調用的時候會是:

import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchUnsettledList, selectList } from '../store/slice/unsettledList'
import type { AppDispatch } from '../store'

const About = () => {
  const dispatch: AppDispatch = useDispatch()
  const states = useSelector(selectList)

  useEffect(() => {
    if (states.fetched) return
    dispatch(fetchUnsettledList())
  }, [])
  
  // 下略...

接著把資料渲染出來就好:

<ul>
  {states.unsettledList.map((item) => (
    <li key={item.id}>
      <a href={`${item.id}/?bview=ASSIGN`}>
        <img src={item.icon} alt="" />
        <div>
          <p>{item.name}</p>
          <p>待處理項目:{item.count}</p>
        </div>
      </a>
    </li>
  ))}
</ul>

以上就是這次的開發,其實跟平常在寫前端是一樣的。


上一篇
Day 15 | Nuxt3 串接 kintone
下一篇
Day 17 | 案例分享:Flutter 串接 kintone 打卡
系列文
我阿嬤都會的 kintone 客製化開發30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言